昨天介紹 jwt token 的 backend 實作, 今天介紹 frontend
前端處理 jwt的方式就是, 接收到後端來的 token及使用者id資料, 將這兩筆資料存到localStorage裡面, 之後需要驗證的時候, 就從localStorage拿出來, 放到 headers 裡面, 打出去給 backend api驗證
但這邊 backend 會需要兩隻api, 所以在本節我們就順便簡單實作一下 /signup /signin (先不用資料庫, 把帳密存在記憶體裡)
小提醒:這次篇幅會比較大, 因為包含了 api實作
因為要實作前端, 所以用 fiber 搭了一個簡易的 api
package main
import (
	"encoding/json"
	"fmt"
	"log"
	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/cors"
)
const port = ":8886"
// 記憶體中的臨時資料庫 users
var userCount int
var users []User
// 臨時資料庫 posts
var postCount int
var posts []Post
func main() {
	api := fiber.New()
	api.Use(cors.New())
	api.Post("/signup", Signup)
	api.Post("/signin", Signin)
	api.Post("/post", Postt)
	if err := api.Listen(port); err != nil {
		log.Fatal(err)
	}
}
// handle request
func Signup(c *fiber.Ctx) error {
	// bodyparser 負責把 request body 存到 User{} 結構裡面
	user := User{}
	err := c.BodyParser(&user)
	if err != nil {
		return err
	}
	userCount += 1 // 這邊就是類似 auto increment 的功能 
	user.Id = userCount // 把資料加上一個序號
	users = append(users, user) // user 加到 users 裡
	jsonFormat, err := json.MarshalIndent(users, "", "  ")
	if err != nil {
		return err
	}
	fmt.Println(string(jsonFormat))
	return c.JSON(user) // 把加入的 user 資料 return 回去給使用者
}
func Signin(c *fiber.Ctx) error {
	user := User{}
	err := c.BodyParser(&user)
	if err != nil {
		return err
	}
	// 一開始登入flag false
	loginSuccess := false
	// 進臨時資料庫比對 先找到 email, 再比對 password
	for _, item := range users {
		if user.Email == item.Email {
			if user.Password == item.Password {
				loginSuccess = true // flag 設為 true
				user.Id = item.Id // 把 id 加到我們要用的 user資料裡
			}
		}
	}
	var token string
	if loginSuccess {
		// 產生 jwt token 給前端使用者
		token, err = genJWT(user.Id, user.Email)
		if err != nil {
			return err
		}
		fmt.Println(token)
	} else {
		return c.JSON(fiber.Map{"message": "invalid email or password"})
	}
	// 把userid 及 token 返回, 前端需要存到 localStorage
	return c.JSON(fiber.Map{
		"id":    user.Id,
		"token": token,
	})
}
func Postt(c *fiber.Ctx) error {
	// 將發文資料拿出來
	post := Post{}
	fmt.Println("in post")
	err := c.BodyParser(&post)
	if err != nil {
		fmt.Println(err.Error())
		return err
	}
	fmt.Println(post)
	token := c.Get("Authorization")
	fmt.Println("token: ")
	fmt.Println(token)
	// decode jwt 看 token secret 有沒有被竄改過
	claim, err := DecodeJWT(token)
	if err != nil {
		return c.JSON(fiber.Map{"message": "token invalid"})
	}
	// 這邊進資料庫看 id 是否存在, 存在則驗證通過
	authSuccess := false
	for _, item := range users {
		if item.Id == claim.Id {
			authSuccess = true
		}
	}
	// 這邊把 post 加入臨時資料庫 posts
	var resultPost Post
	if authSuccess {
		postCount += 1
		resultPost = Post{
			Id:       postCount,
			Title:    post.Title,
			Body:     post.Body,
			PostedBy: claim.Id,
		}
		posts = append(posts, resultPost)
	}
	return c.JSON(fiber.Map{
		"post_id":   resultPost.Id,
		"title":     resultPost.Title,
		"body":      resultPost.Body,
		"posted_by": resultPost.PostedBy,
	})
}
// types
type User struct {
	Id       int    `json:"id"`
	Email    string `json:"email"`
	Password string `json:"password"`
}
type Post struct {
	Id       int    `json:"post_id"`
	Title    string `json:"title"`
	Body     string `json:"body"`
	PostedBy int
}
import { useState } from 'react'
function App() {
  const [email, setEmail] = useState("")
  const [password, setPassword] = useState("")
  const [title, setTitle] = useState("")
  const [body, setBody] = useState("")
  function signupPost({email,password}){
    
    fetch("http://localhost:8886/signup",{
      method:"POST",
      headers:{"Content-Type":"application/json"},
      body:JSON.stringify({email,password})
    })
    .then(res=>res.json())
    .then(data=>{
      console.log(data)
    })
    .catch(err=>{
      console.log(err)
    })
  }
  function signinPost({email,password}){
    fetch("http://localhost:8886/signin",{
      method:"POST",
      headers:{"Content-Type":"application/json"},
      body:JSON.stringify({email,password})
    })
    .then(res=>res.json())
    .then(data=>{
      console.log(data)
      localStorage.setItem("id",data.id)
      localStorage.setItem("token",data.token)
    })
    .catch(err=>{
      console.log(err)
    })
  }
  function postPost({title,body}){
    const id = localStorage.getItem("id")
    const token = localStorage.getItem("token")
    fetch("http://localhost:8886/post",{
      method:"POST",
      headers:{
        "Content-Type":"application/json",
        "Authorization":token
      },
      body:JSON.stringify({id,title,body})
    })
    .then(res=>res.json())
    .then(data=>{
      console.log(data)
    })
    .catch(err=>{
      console.log(err)
    })
  }
  return (
    <>
      <div>
        註冊
        <input 
          type="text"
          placeholder="email"
          onChange={(e)=>{
            setEmail(e.target.value)
          }}
        />
        <input 
          type="text"
          placeholder="password"
          onChange={(e)=>{
            setPassword(e.target.value)
          }}
        />
        <input 
          type="button"
          value="submit"
          onClick={(e)=>{
            e.preventDefault()
            signupPost({email,password})
          }}
        />
      </div>
      <div>
        登入
        <input 
          type="text"
          placeholder="email"
          onChange={(e)=>{
            setEmail(e.target.value)
          }}
        />
        <input 
          type="text"
          placeholder="password"
          onChange={(e)=>{
            setPassword(e.target.value)
          }}
        />
        <input 
          type="button"
          value="submit"
          onClick={(e)=>{
            e.preventDefault()
            signinPost({email,password})
          }}
        />
      </div>
      <br />
      <div>
        發文
        <input 
          type="text"
          placeholder="title"
          onChange={(e)=>{
            setTitle(e.target.value)
          }}
        />
        <input 
          type="text"
          placeholder="body"
          onChange={(e)=>{
            setBody(e.target.value)
          }}
        />
        <input 
          type="button"
          value="submit"
          onClick={(e)=>{
            e.preventDefault()
            postPost({title,body})
          }}
        />
      </div>
    </>
  )
}
export default App